<?php

/**
 * uniBoard Full Parser
 *
 * Light and fast forum solution for web 2.0-like communities.
 *
 * @package		uniBoard
 * @author		Rafał Pitoń
 * @license		http://www.gnu.org/licenses/gpl-3.0.html
 * @since		Version 0.1
 * 
 * ------------------------------------------------------------------------
 * 
 * $Date: 2010-09-20 21:23:27 +0000 (Mon, 20 Sep 2010) $
 * $Revision: 64 $
 * $Author: rafio.xudb@gmail.com $
 */

// ------------------------------------------------------------------------

class class_parser{
	
	/**
	 * Main class pointer
	 *
	 * @var object
	 */
	
	private $appCore			= NULL;
	
	/**
	 * Parsing Key (we use it to avoid collisions)
	 *
	 * @var string
	 */
	
	private $parsing_key		= '';
	
	/**
	 * Parsing Data (like bbcodes, smileys and badwords)
	 *
	 * @var object
	 */
	
	private $parsing_data		= array();
	
	/**
	 * Parsing Regex
	 *
	 * @var string
	 */
	
	private $parsing_regex		= '';
	
	/**
	 * Parsing Cursor
	 *
	 * @var integer
	 */
	
	private $parsing_cursor		= 0;
	
	/**
	 * Parsing Pause
	 *
	 * @var integer
	 */
	
	private $parsing_pause		= 0;
	
	/**
	 * Parsing Trim
	 *
	 * @var integer
	 */
	
	private $parsing_trim		= 0;
	
	/**
	 * Parsing nl2br
	 *
	 * @var integer
	 */
	
	private $parsing_nl2br		= 0;
	
	/**
	 * Parsing Stack
	 *
	 * @var array
	 */
	
	private $parsing_stack		= array();
	
	/**
	 * Text we are working on
	 *
	 * @var string
	 */
	
	private $parsed_string		= '';
	
	/**
	 * identifyTag: BB Code
	 *
	 * @var integer
	 */
	
	private $found_bbcode		= false;
	
	/**
	 * identifyTag: BB Code with param
	 *
	 * @var integer
	 */
	
	private $found_bbcode_param	= false;
	
	/**
	 * identifyTag: Smiley
	 *
	 * @var integer
	 */
	
	private $found_smiley		= false;
	
	/**
	 * Number of Images in parsed code
	 *
	 * @var integer
	 */
	
	public $text_images			= 0;
	
	/**
	 * Number of Smileys in parsed code
	 *
	 * @var integer
	 */
	
	public $text_smileys		= 0;
	
	/**
	 * Number of GeSHi tags in parsed code
	 *
	 * @var integer
	 */
	
	public $text_geshi			= 0;
	
	/**
	 * Number of links in parsed code
	 *
	 * @var integer
	 */
	
	public $text_links			= 0;
	
	/**
	 * Parsed text requires post_parsing?
	 *
	 * @var integer
	 */
	
	private $post_parse			= 0;
	
	/**
	 * URL Regex
	 *
	 * @var string
	 */
	
	private $link_regex			= '((((ftp|https|http)://www\.)|((ftp|https|http)://)|(www\.))(\w|\-|\.)+(\.[a-zA-Z]{2}[a-zA-Z]?[a-zA-Z])?((/(((\w)|(\.)|(&amp;)|(&)|(=)|(\?))+)?(\#(\w)+)?)+)?)';
	
	/**
	 * Attachment data
	 *
	 * @var array
	 */
	
	private $attachments		= NULL;
	
	// ------------------------------------------------------------------------

	/**
	 * Initialise Class
	 *
	 * @param object $appCore
	 * @param bool $final_parse
	 */
	
	function __construct( $appCore, $final_parse = true){
	
		// Store appCore pointer
		$this -> appCore = $appCore;
	
		// Make Key
		$this -> parsing_key = $appCore -> makeSalt( 6, false);
		
		// Load Parsing Data
		$this -> parsing_data = class_cache::loadCache('parsing_data');

		// Build Parsing Data?
		if ( class_cache::$load_error)
		{
			// Set empty data
			$this -> parsing_data = array(
				0 => array(),	// BBCodes
				1 => 0,			// BBCodes Number
				2 => array(),	// Smileys Find
				3 => array(),	// Smileys Replace
				4 => 0,			// Smileys Number
				5 => array(),	// Keywords Find
				6 => array(),	// Keywords Replace
				7 => array(),	// GeSHi Languages
				8 => 0			// GeSHi Languages Number
			);
			
			// Select BB Codes
			$appCore -> db -> query( 'SELECT bbcode_id, bbcode_tag, bbcode_type, bbcode_replace, bbcode_template, bbcode_noparse, bbcode_trim, bbcode_nl2br
			FROM ' . DB_PREFIX . 'bbcodes');
			
			// Put in Parsing Data
			while ( $bbcode_result = $appCore -> db -> fetch_assoc())
			{
				// BB Code needs finalisation?
				$bbcode_result['bbcode_finalise'] = isset($bbcode_result['bbcode_template'][0]);
				
				// Add BBCode to list
				$this -> parsing_data[0][] = $bbcode_result;
								
				// Increase counter
				$this -> parsing_data[1] ++;
			}
			
			// Free resources
			$appCore -> db -> freeResult();
			
			// Select Smileys
			$appCore -> db -> query( 'SELECT smiley_replace, smiley_image, smiley_width, smiley_height, smiley_name
			FROM ' . DB_PREFIX . 'smileys
			ORDER BY smiley_replace DESC');
			
			// Put in Parsing Data
			while ( $smiley_result = $appCore -> db -> fetch_array())
			{
				// Add string to find
				$this -> parsing_data[2][] = mb_strtolower( htmlspecialchars($smiley_result[0]));
				
				// Add its replacement
				$this -> parsing_data[3][] = '<img src="<SMILEYS_DIR>' . htmlspecialchars($smiley_result[1]) . '" alt="' . htmlspecialchars($smiley_result[4]) . '" title="' . htmlspecialchars($smiley_result[4]) . '" width="' . htmlspecialchars($smiley_result[2]) . '" height="' . htmlspecialchars($smiley_result[3]) . '" />';
				
				// Increase counter
				$this -> parsing_data[4] ++;
			}
			
			// Free resources
			$appCore -> db -> freeResult();
			
			// Select Badwords
			$appCore -> db -> query( 'SELECT word_find, word_replace
			FROM ' . DB_PREFIX . 'badwords
			ORDER BY word_find DESC');
			
			// Put in Parsing Data
			while ( $badword_result = $appCore -> db -> fetch_array())
			{
				// Add string to find
				$this -> parsing_data[5][] = htmlspecialchars($badword_result[0]);
				
				// Add its replacement
				$this -> parsing_data[6][] = htmlspecialchars($badword_result[1]);
			}
			
			// Free resources
			$appCore -> db -> freeResult();
			
			// Select GeSHi Languages
			$appCore -> db -> query( 'SELECT geshi_tag, geshi_file
			FROM ' . DB_PREFIX . 'bbcodes_geshi
			ORDER BY geshi_tag');
			
			// Put in Parsing Data
			while ( $geshi_result = $appCore -> db -> fetch_array())
			{
				// Add GeSHi to list
				$this -> parsing_data[7][strtolower($geshi_result[0])] = strtolower($geshi_result[1]);
								
				// Increase counter
				$this -> parsing_data[8] ++;
			}
			
			// Free resources
			$appCore -> db -> freeResult();
			
			// Save parsing data
			class_cache::saveCache('parsing_data', $this -> parsing_data);
		}
		
		// Sortout templates in bbcodes?
		if ( !IN_ADMIN && !isset($this -> appCore -> templates['bbcodes']))
		{
			// Start Templates
			$templates = new tpl_bbcode($appCore);
			
			// Reiterate bbcodes
			for ( $i = 0; $i < $this -> parsing_data[1]; $i ++)
			{
				if ( isset($this -> parsing_data[0][$i]['bbcode_template'][0]))
				{
					// Template name
					$tpl_name = 'tpl_' . $this -> parsing_data[0][$i]['bbcode_template'];
					
					// Set template?
					if ( method_exists($templates, $tpl_name))
					{
						$this -> parsing_data[0][$i]['bbcode_replace'] = $templates -> $tpl_name();
						
						// Replace keys?
						if ( $final_parse)
						{
							if ( $this -> parsing_data[0][$i]['bbcode_type'] == 1)
							{
								$this -> parsing_data[0][$i]['bbcode_replace'] = str_replace( '{param}', '\\1', $this -> parsing_data[0][$i]['bbcode_replace']);
								$this -> parsing_data[0][$i]['bbcode_replace'] = str_replace( '{content}', '\\2', $this -> parsing_data[0][$i]['bbcode_replace']);
							}
							else
							{
								$this -> parsing_data[0][$i]['bbcode_replace'] = str_replace( '{content}', '\\1', $this -> parsing_data[0][$i]['bbcode_replace']);
							}
						}
					}
					// Unset template
					unset( $this -> parsing_data[0][$i]['bbcode_template']);
				}
			}
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Completely parses selected string
	 * WARNING!
	 * This function is much easier to use than other ones.
	 * You dont care about all that complicated stuff like
	 * Perparsing, finalisation, etc. ect.
	 * HOWEVER
	 * This function is awesome resources hog, dont make
	 * it parse more than one string longer than 20kb or
	 * it will eat memory and processor time at big rate.
	 * 
	 * Use it only when you need to parse and display text AT SAME TIME
	 * Good uses are all "preview" pages.
	 * 
	 * ALWAYS use preparse() before saving raw string to DB
	 * ALWAYS use finalise() on drawing preparsed string from DB!
	 *
	 * @param string $string
	 * @param array $parse_params
	 * @return string
	 */
	
	function complete( $string, $parse_params = array()){
				
		// Reset stack
		$this -> parsing_cursor = 0;
		$this -> parsing_stack = array();
		
		// Reset stats
		$this -> text_images = 0;
		$this -> text_smileys = 0;
		$this -> text_geshi = 0;
		$this -> text_links = 0;
			
		// Build regex
		if ( !isset($this -> parsing_regex[0]))
		{
			$this -> buildParsingRegex( $parse_params);
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Pre-parses raw string for DB storage
	 *
	 * @param string $string
	 * @param array $parse_params
	 * @return string
	 */
	
	function preparse( $string, $parse_params = array()){
		
		// Reset stats
		$this -> text_images = 0;
		$this -> text_smileys = 0;
		$this -> text_geshi = 0;
		$this -> text_links = 0;
			
		// Build regex
		if ( !isset($this -> parsing_regex[0]))
		{
			$this -> buildParsingRegex( $parse_params);
		}
		
		// Set new String
		$this -> parsed_string = htmlspecialchars( $string);
		
		// Reset stack for first cycle
		$this -> parsing_cursor = 0;
		$this -> parsing_pause = 0;
		$this -> parsing_trim = 0;
		$this -> parsing_nl2br = 0;
		$this -> parsing_stack = array();
		
		// First Parse Cycle?
		if (isset($this -> parsing_regex[0]))
		{
			preg_replace_callback( '#' . $this -> parsing_regex . '#si', array($this, 'parseFirstCycle'), $this -> parsed_string);
		}
		
		// Cursor not at end of string?
		if ( $this -> parsing_cursor < strlen( $this -> parsed_string))
		{
			// Get remaining text we will fix
			$last_text = substr($this -> parsed_string, $this -> parsing_cursor);
			
			// Do linebreaking
			$this -> parsed_string = substr_replace( $this -> parsed_string, nl2br( $last_text), $this -> parsing_cursor, strlen( $last_text));
		}
		
		// Reset for second cycle
		$this -> parsing_cursor = 0;
		$this -> parsing_pause = 0;
		$this -> parsing_trim = 0;
		$this -> parsing_nl2br = 0;
		$this -> parsing_stack = array();
		
		// Second Parse Cycle
		if (isset($this -> parsing_regex[0]))
		{
			preg_replace_callback( '#' . $this -> parsing_regex . '#si', array($this, 'parseSecondCycle'), $this -> parsed_string);
		}
		
		// Return parsed string
		return ($this -> text_smileys > 0 ? '1' : '0').($this -> post_parse).$this -> parsed_string;
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Finalises Pre-parsed string
	 *
	 * @param string $string
	 * @param string $highlight
	 * @return string
	 */
	
	function finalise( $string, $highlight = ''){
		
		// Check if there is a point in parsing string?
		if ( isset( $string[2]))
		{
			// There are two digits at beginning of each pre-parsed text
			// First digit tells us to replace <SMILEYS_DIR> with correct directory to smileys
			// Second digit tells us we have bbcodes that require second loop to put them into templates
			
			// Fix smileys
			if ( $string[0] == 1)
			{
				$string = str_replace( '<SMILEYS_DIR>', SMILEYS_PATH, $string);
			}
			
			//  Highlight stuff?
			if ( isset( $highlight[0]))
			{
				// Replace stuff
				$string = preg_replace('#' . preg_quote($highlight, '#') . '#si', '{HL:' . $this -> parsing_key . '}\\0{/HL:' . $this -> parsing_key . '}', $string);
				
				// Reverse highlight inside <> so it wont mess up rest of BBCodes
				$string = preg_replace_callback( '#<(.*?)>#si', array($this, 'fixHighlight'), $string);
				
				// Fix highlight
				$string = str_replace( array('{HL:' . $this -> parsing_key . '}', '{/HL:' . $this -> parsing_key . '}'), array('<span class="highlight">', '</span>'), $string);
			}
			
			// Censore Stuff?
			if ( isset( $this -> parsing_data[4][0]))
			{
				$string = str_ireplace( $this -> parsing_data[6], $this -> parsing_data[7], $string);
			}
			
			// Fix BB Codes
			if ( $string[1] == 1)
			{
				// Reiterate Our BBCodes tags
				for ( $i = 0; $i < $this -> parsing_data[1]; $i ++)
				{
					// Finalise BBCode?
					if ( $this -> parsing_data[0][$i]['bbcode_finalise'] && strpos( $string, '<code id="' . $this -> parsing_data[0][$i]['bbcode_id'] . '"') !== false)
					{
						// Do Replace
						switch ($this -> parsing_data[0][$i]['bbcode_type'])
						{
							case 0:
								$string = preg_replace( '#<code id="' . $this -> parsing_data[0][$i]['bbcode_id'] . '">(.*?)</code id="' . $this -> parsing_data[0][$i]['bbcode_id'] . '">#si', $this -> parsing_data[0][$i]['bbcode_replace'], $string);
							break;
							case 1:
								$string = preg_replace( '#<code id="' . $this -> parsing_data[0][$i]['bbcode_id'] . '" param="(.*?)">(.*?)</code id="' . $this -> parsing_data[0][$i]['bbcode_id'] . '">#si', $this -> parsing_data[0][$i]['bbcode_replace'], $string);
							break;
							case 2:
								
								$string = str_replace( '<code id="' . $this -> parsing_data[0][$i]['bbcode_id'] . '"/>', $this -> parsing_data[0][$i]['bbcode_replace'], $string);
							break;
						}
					}
				}
			}
			
			// Return string without those digits
			return substr( $string, 2);
		}
		else
		{
			return substr( $string, 2);
		}
		
	}
		
	// ------------------------------------------------------------------------

	/**
	 * Builds parsing regex
	 *
	 * @param array $parse_params
	 */
	
	function buildParsingRegex( $parse_params){
		
		// Add BBCodes?
		if ( isset($parse_params['bbcode']) && $parse_params['bbcode'])
		{
			// Reiterate BB Codes
			for ( $i = 0; $i < $this -> parsing_data[1]; $i ++)
			{
				if (
					($this -> parsing_data[0][$i]['bbcode_id'] < 15)
					||
					(isset($parse_params['geshi']) && $parse_params['geshi'] && $this -> parsing_data[0][$i]['bbcode_id'] == 15)
					||
					(isset($parse_params['urls']) && $parse_params['urls'] && $this -> parsing_data[0][$i]['bbcode_id'] > 20 && $this -> parsing_data[0][$i]['bbcode_id'] < 23)
					||
					(isset($parse_params['images']) && $parse_params['images'] && $this -> parsing_data[0][$i]['bbcode_id'] == 23)
					||
					(isset($parse_params['youtube']) && $parse_params['youtube'] && $this -> parsing_data[0][$i]['bbcode_id'] == 24)
					||
					($this -> parsing_data[0][$i]['bbcode_id'] > 24)
					||
					($this -> parsing_data[0][$i]['bbcode_id'] > 15 && $this -> parsing_data[0][$i]['bbcode_id'] < 21)
				)
				{
					// BBCode type
					switch ($this -> parsing_data[0][$i]['bbcode_type'])
					{
						case 0:
							$this -> parsing_regex .= '|\[' . preg_quote($this -> parsing_data[0][$i]['bbcode_tag'], '#') . '\]';
							$this -> parsing_regex .= '|\[/' . preg_quote($this -> parsing_data[0][$i]['bbcode_tag'], '#') . '\]';
						break;
						case 1:
							$this -> parsing_regex .= '|\[' . preg_quote($this -> parsing_data[0][$i]['bbcode_tag'], '#') . '=&quot(.*?)&quot;\]';
							$this -> parsing_regex .= '|\[/' . preg_quote($this -> parsing_data[0][$i]['bbcode_tag'], '#') . '\]';
						break;
						case 2:
							$this -> parsing_regex .= '|\[' . preg_quote($this -> parsing_data[0][$i]['bbcode_tag'], '#') . '\]';
						break;
					}
				}
			}
			
			// Automatical URL replace?
			if ( isset($parse_params['urls']) && $parse_params['urls'] && isset($parse_params['links']) && $parse_params['links'])
			{
				$this -> parsing_regex .= '|' . $this -> link_regex;
			}
		}
		
		// Add Smileys?
		if ( isset($parse_params['smileys']) && $parse_params['smileys'])
		{
			// Reiterate Smileys
			for ( $i = 0; $i < $this -> parsing_data[4]; $i ++)
			{
				$this -> parsing_regex .= '|' . preg_quote( $this -> parsing_data[2][$i], '#');
			}
		}
		
		// Trim?
		if ( isset($this -> parsing_regex[0]))
		{
			$this -> parsing_regex = substr($this -> parsing_regex, 1);
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * First cycle parser trims BB Code content and linebreaks
	 *
	 * @param array $matches
	 */
	
	function parseFirstCycle($matches){
		
		// Cursor position
		$cursor = strpos( $this -> parsed_string, $matches[0], $this -> parsing_cursor);
		
		// Is it BB Code, smiley or url?
		if ( $this -> identifyTag( $matches[0]) && $this -> found_bbcode !== false)
		{
			// Parsing stack empty?
			if ($cursor > 0 && empty( $this -> parsing_stack))
			{
				// Text bit
				$text_bit = substr( $this -> parsed_string, $this -> parsing_cursor, $cursor - $this -> parsing_cursor);
				
				// Break lines between last cursor pos and our pos
				$this -> parsed_string = substr_replace( $this -> parsed_string, nl2br( $text_bit), $this -> parsing_cursor, $cursor - $this -> parsing_cursor);
				
				// Set Cursor position
				$this -> parsing_cursor = $cursor + (strlen(nl2br($text_bit)) - strlen($text_bit));
				
				// Refresh our position
				$cursor = strpos( $this -> parsed_string, $matches[0], $this -> parsing_cursor);
			}
			
			// We dont care if we are in BB Code type 2
			if ( $this -> parsing_data[0][$this -> found_bbcode]['bbcode_type'] != 2)
			{							
				// BB Code aint loner, are we opening or closing stack?
				if ( isset( $matches[0][1]) && $matches[0][1] == '/')
				{	
					// Pop last tag
					$prev_code = array_pop( $this -> parsing_stack);
						
					// Tags match?
					if ( $prev_code[0] == $this -> parsing_data[0][$this -> found_bbcode]['bbcode_tag'])
					{
						// Unset stuff
						if ( $this -> parsing_data[0][$this -> found_bbcode]['bbcode_noparse'])
						{
							$this -> parsing_pause --;
						}
						
						if ( !$this -> parsing_data[0][$this -> found_bbcode]['bbcode_trim'])
						{
							$this -> parsing_trim --;
						}
						
						if ( !$this -> parsing_data[0][$this -> found_bbcode]['bbcode_nl2br'])
						{
							$this -> parsing_nl2br --;
						}
						
						// Give love to text between us?
						if ( $this -> parsing_pause == 0 && $prev_code[1] < $cursor)
						{
							// Grab text between previous tag and our one
							$text_bit = substr( $this -> parsed_string, $this -> parsing_cursor, $cursor - $this -> parsing_cursor);
							
							// Original textbit lengt
							$text_bit_len = strlen($text_bit);
							
							// Fix it
							if ( $this -> parsing_trim == 0 && $this -> parsing_data[0][$this -> found_bbcode]['bbcode_trim'])
							{
								$text_bit = trim($text_bit);
								
							}
							
							if ( $this -> parsing_nl2br == 0 && $this -> parsing_data[0][$this -> found_bbcode]['bbcode_nl2br'])
							{
								$text_bit = nl2br($text_bit);
							}
							
							// Put it back between our tags
							$this -> parsed_string = substr_replace( $this -> parsed_string, $text_bit, $this -> parsing_cursor, $cursor - $this -> parsing_cursor);
							
							// Set Cursor position
							$this -> parsing_cursor = $cursor + (strlen($text_bit) - $text_bit_len) + strlen($matches[0]);
						}
					}
					else
					{
						$this -> parsing_cursor = $cursor + strlen($matches[0]);
					}
				}
				else
				{	
					// There is other tag before us?
					if ( !empty($this -> parsing_stack) && $this -> parsing_pause == 0)
					{
						// Previous code
						$prev_code = end( $this -> parsing_stack);
						
						// Is there any difference?
						if ( $prev_code[1] < $cursor)
						{
							// Grab text between previous tag and our one
							$text_bit = substr( $this -> parsed_string, $this -> parsing_cursor, $cursor - $this -> parsing_cursor);
							
							// Original text-bit lenght
							$text_bit_len = strlen($text_bit);
							
							// Is it ealier than our cursor, meaning there is closing item between?
							if ( $prev_code[1] < $this -> parsing_cursor)
							{
								// [/TAG]<--[rtrim,nl2br]-->[TAG]
								if ( $this -> parsing_trim == 0)
								{
									$text_bit = rtrim($text_bit);
								}
								
								if ( $this -> parsing_nl2br == 0)
								{
									$text_bit = nl2br($text_bit);
								}
							}
							else
							{
								// [TAG]<--[ltrim,nl2br]-->[TAG]
								if ( $this -> parsing_trim == 0)
								{
									$text_bit = ltrim($text_bit);
								}
								
								if ( $this -> parsing_nl2br == 0)
								{
									$text_bit = nl2br($text_bit);
								}
							}
							
							// Put it back between our tags
							$this -> parsed_string = substr_replace( $this -> parsed_string, $text_bit, $this -> parsing_cursor, $cursor - $this -> parsing_cursor);

							// Set Cursor position
							$this -> parsing_cursor = $cursor + (strlen($text_bit) - $text_bit_len) + strlen($matches[0]);
						}
					}
					
					// Set Cursor position
					$this -> parsing_cursor = $cursor + strlen($matches[0]);
							
					// Add Us to Stack
					$this -> parsing_stack[] = array(
						0 => $this -> parsing_data[0][$this -> found_bbcode]['bbcode_tag'], // Previous tag type
						1 => $cursor + strlen($matches[0])  // Previous tag position
					);
										
					// Set some stuff like pause, trim and break
					if ( $this -> parsing_data[0][$this -> found_bbcode]['bbcode_noparse'])
					{
						$this -> parsing_pause ++;
					}
					
					if ( !$this -> parsing_data[0][$this -> found_bbcode]['bbcode_trim'])
					{
						$this -> parsing_trim ++;
					}
					
					if ( !$this -> parsing_data[0][$this -> found_bbcode]['bbcode_nl2br'])
					{
						$this -> parsing_nl2br ++;
					}
				}
			}
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Second cycle parser replaces bbcodes
	 *
	 * @param array $matches
	 */
	
	function parseSecondCycle($matches){
		
		// Cursor position
		$cursor = strpos( $this -> parsed_string, $matches[0], $this -> parsing_cursor);
		
		// Identify BB Code
		if ( $this -> identifyTag( $matches[0]))
		{
			// Is it Smiley?
			if ( $this -> found_smiley !== false && $this -> parsing_pause == 0)
			{
				// Increase smileys counter
				$this -> text_smileys ++;
				
				// Return our replacement
				$this -> parsed_string = substr_replace($this -> parsed_string, $this -> parsing_data[3][$this -> found_smiley], $cursor, strlen($matches[0]));
				
				// Move Cursor forward
				$this -> parsing_cursor = $cursor + (strlen( $this -> parsing_data[3][$this -> found_smiley]) - strlen($matches[0]));
			}
			else if ($this -> found_bbcode !== false)
			{
				// Solo URL needs special care
				if ($this -> found_bbcode == -1)
				{
					// Replace?
					if ( $this -> parsing_pause == 0)
					{
						// Increase links counter
						$this -> text_links ++;
						
						// Put link into text
						$this -> parsed_string = substr_replace($this -> parsed_string, '<code id="36"/>' . $this -> appCore -> fixLink($matches[0]) . '</code id="36">', $cursor, strlen($matches[0]));
						
						// Move Cursor forward
						$this -> parsing_cursor = $cursor + (strlen('<code id="36"/>' . $this -> appCore -> fixLink($matches[0]) . '</code id="36">') - strlen($matches[0]));
					}
				}
				else if ( $this -> parsing_data[0][$this -> found_bbcode]['bbcode_type'] == 2)
				{
					// Replace?
					if ( $this -> parsing_pause == 0)
					{
						// Return our replacement
						$this -> parsed_string = substr_replace($this -> parsed_string, ($this -> parsing_data[0][$this -> found_bbcode]['bbcode_finalise'] ? '<code id="' . $this -> parsing_data[0][$this -> found_bbcode]['bbcode_id'] . '"/>' : $this -> parsing_data[0][$this -> found_bbcode]['bbcode_replace']), $cursor, strlen($matches[0]));

						// Move Cursor forward
						$this -> parsing_cursor = $cursor + (strlen($this -> parsing_data[0][$this -> found_bbcode]['bbcode_finalise'] ? '<code id="' . $this -> parsing_data[0][$this -> found_bbcode]['bbcode_id'] . '"/>' : $this -> parsing_data[0][$this -> found_bbcode]['bbcode_replace']) - strlen($matches[0]));
						
						// Tell It that we need finalisation?
						if ($this -> parsing_data[0][$this -> found_bbcode]['bbcode_finalise'])
						{
							$this -> post_parse = 1;
						}
					}
				}
				else if ( isset($matches[0][1]) && $matches[0][1] == '/')
				{
					// Grab previous Stack item
					$prev_item = array_pop( $this -> parsing_stack);
					
					// Items match?
					if ( $prev_item[0] == $this -> parsing_data[0][$this -> found_bbcode]['bbcode_tag'])
					{
						// Unpause parsing?
						if ( $this -> parsing_data[0][$this -> found_bbcode]['bbcode_noparse'])
						{
							$this -> parsing_pause --;
						}
						
						// Return ready BB Code
						if ( $this -> parsing_pause == 0)
						{
							// BB Code Value
							$bbcode_replace = $this -> parseTag( $prev_item[4], substr($this -> parsed_string, $prev_item[1] + $prev_item[2], $cursor - $prev_item[1] - $prev_item[2]), $prev_item[3]);
							
							// Put BB Code
							$this -> parsed_string = substr_replace($this -> parsed_string, $bbcode_replace, $prev_item[1], $cursor - $prev_item[1] + strlen($matches[0]));
							
							// Move Cursor forward
							$this -> parsing_cursor = $cursor + strlen($bbcode_replace) - ($cursor - $prev_item[1] + strlen($matches[0]));
						}
					}
					else
					{
						// Put it back where it was
						array_push( $this -> parsing_stack, $prev_item);
					}
				}
				else
				{
					// Get Param?
					if ( $this -> parsing_data[0][$this -> found_bbcode]['bbcode_type'] == 1)
					{
						// BB Code contains =quot fragment?
						if ( strpos( $matches[0], '=&quot;') !== false)
						{
							//Grab half of param
							$param = trim(substr( $matches[0], strpos( $matches[0], '=&quot;') + 7));
							
							// Cut it?
							if ( isset($param[5]) && substr( $param, strlen($param) - 7) === '&quot;]')
							{
								$param = trim(substr( $param, 0, strlen($param) - 7));
							}
							else
							{
								$param = '';
							}
						}
						else
						{
							$param = '';
						}
					}
					else
					{
						// Param is empty
						$param = '';
					}
					
					// Add BB Code to stack
					$this -> parsing_stack[] = array(
						0	=> $this -> parsing_data[0][$this -> found_bbcode]['bbcode_tag'], // BB Code tag
						1	=> $cursor, // BB Code Position
						2	=> strlen($matches[0]), // BB Code Lenght
						3	=> $param, // BB Code Param
						4	=> $this -> found_bbcode, // BB Code ID
					);
					
					// Pause parsing?
					if ( $this -> parsing_data[0][$this -> found_bbcode]['bbcode_noparse'])
					{
						$this -> parsing_pause ++;
					}
					
					// Move Cursor forward
					$this -> parsing_cursor = $cursor + strlen($matches[0]);
				}
			}
		}
		
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Identifies BB Code by tag
	 *
	 * @param string $text
	 * @return mixed
	 */
	
	function identifyTag( $text){
	
		// Reset stats
		$this -> found_bbcode = false;
		$this -> found_smiley = false;
		
		// Check for bracket
		if ( $text[0] == '[')
		{
			// Is last character a closing bracket?
			if ( substr( $text, strlen($text) - 1) == ']')
			{
				// Get tag
				$tag = mb_strtolower( substr( $text, ($text[1] == '/' ? 2 : 1), strlen($text) - ($text[1] == '/' ? 3 : 2)));
			
				// Tag type
				$closing_tag = isset($text[1]) && $text[1] == '/';
				
				// With param?
				$tag_param = false;
				
				// Cut it off the param?
				if ( strpos($tag, '=') !== false && strpos($tag, '=') > 0)
				{
					// Also check param
					$tag_param = true;
					
					// Fix Tag Id
					$tag = substr( $tag, 0, strpos($tag, '='));
				}
				
				// Tag not empty?
				if ( isset($tag[0]))
				{
					// Loop trought our bbcodes
					for ( $i = 0; $i < $this -> parsing_data[1]; $i ++)
					{
						// Match?
						if ( $tag == $this -> parsing_data[0][$i]['bbcode_tag'] && ($closing_tag || ($tag_param && $this -> parsing_data[0][$i]['bbcode_type'] == 1) || (!$tag_param  && $this -> parsing_data[0][$i]['bbcode_type'] == 0)))
						{
							// We got it!
							$this -> found_bbcode = $i;
							
							// Return success
							return true;
						}
					}
				}
			}
		}
		
		// If we are there, we gonna compare our match to url formula
		if ( preg_match('#^' . $this -> link_regex . '$#i', $text))
		{
			// BB Code is -1
			$this -> found_bbcode = -1;
			
			// But we got it!
			return true;
		}
		
		// And dientify smiley
		for ($i = 0; $i < $this -> parsing_data[4]; $i ++)
		{
			// Match?
			if ( mb_strtolower( $text) == $this -> parsing_data[2][$i])
			{
				// We got it!
				$this -> found_smiley = $i;
				
				// Return success
				return true;
			}
		}
		
		// Return false, we failed to recognise bbcode
		return false;
	}
	
	// ------------------------------------------------------------------------

	/**
	 * Puts tag conent into templates
	 *
	 * @param integer $tag_id
	 * @param string $content
	 * @param string $param
	 * @return string
	 */
	
	function parseTag($tag_id, $content, $param = ''){

		// BB Code requires finalisation?
		if ( $this -> parsing_data[0][$tag_id]['bbcode_finalise'])
		{
			// Special Handle for each tag
			switch ( $this -> parsing_data[0][$tag_id]['bbcode_id'])
			{
				// CODE - just code
				case 14:
					// We will need templates fix on finalisation
					$this -> post_parse = 1;
					
					// Return preparsed code
					return '<code id="' . $this -> parsing_data[0][$tag_id]['bbcode_id'] . '">' . $content . '</code id="' . $this -> parsing_data[0][$tag_id]['bbcode_id'] . '">';
				break;
				
				// CODE - Geshi
				case 15:
					// Parse geshi MF
					if (($this -> appCore -> conf['geshi_blocks'] == 0 || $this -> text_geshi < $this -> appCore -> conf['geshi_blocks']) && ($this -> appCore -> conf['geshi_block_size'] == 0 || $this -> appCore -> conf['geshi_block_size'] > strlen($content)))
					{
						// Param to lower
						$param = strtolower( $param);
						
						// Allright?
						if ( isset($this -> parsing_data[7][$param]) && file_exists(GESHI_DATA_PATH . '/' . $this -> parsing_data[7][$param] . PHP_EXT))
						{
							// We will need templates fix on finalisation
							$this -> post_parse = 1;
							
							// Increase GeSHi Stat
							$this -> text_geshi ++;
						
							// Start Geshi
							$geshi = new class_geshi(htmlspecialchars_decode($content), $this -> parsing_data[7][$param]);
							
							// Return preparsed code
							return '<code id="' . $this -> parsing_data[0][$tag_id]['bbcode_id'] . '" param="' . $param . '">' . $geshi -> parse_code() . '</code id="' . $this -> parsing_data[0][$tag_id]['bbcode_id'] . '">';
						}
						else
						{
							// Return it as it is
							return nl2br( $content);
						}
					}
					else
					{
						// Return it as it is
						return nl2br( $content);
					}
				break;
				
				// Quote - noparam
				case 17:
					// We will need templates fix on finalisation
					$this -> post_parse = 1;
					
					// Return preparsed code
					return '<code id="' . $this -> parsing_data[0][$tag_id]['bbcode_id'] . '">' . $content . '</code id="' . $this -> parsing_data[0][$tag_id]['bbcode_id'] . '">';
				break;
				
				// Quote - PARAM
				case 18:
					// We will need templates fix on finalisation
					$this -> post_parse = 1;
					
					// Return preparsed code
					return '<code id="' . $this -> parsing_data[0][$tag_id]['bbcode_id'] . '" param="' . $param . '">' . $content . '</code id="' . $this -> parsing_data[0][$tag_id]['bbcode_id'] . '">';
				break;
			}
		}
		else
		{
			// Some BBCodes require special care anyway
			switch ( $this -> parsing_data[0][$tag_id]['bbcode_id'])
			{
				// Size must be between 10% and 300%
				case 12:
					// Make param integer
					settype( $param, 'integer');
					
					// Make it 10% when its less
					if ( $param < 10)
					{
						$param = 10;
					}
					
					// Make it 300% when its bigger than that
					if ( $param > 300)
					{
						$param = 300;
					}
				break;
				// Color must be correct
				case 13:
					// Param to all lowercase
					$param = strtolower( $param);
					
					// Color whitelists?
					if ( !in_array($param, array('aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'purple', 'red', 'silver', 'teal', 'white', 'yellow')))
					{
						// Color not empty?
						if ( isset($param[4]))
						{
							// Firs character is hash?
							if ($param[0] == '#')
							{
								// Make good param
								$param = '#' . $this -> appCore -> secureFilename( substr( $param, 1), '000000', '');
							}
							else
							{
								$param= '#000000';
							}
						}
						else
						{
							$param= '#000000';
						}
						
						// Color too long?
						if ( isset($param[7]))
						{
							$param= '#000000';
						}
					}
				break;
			}
			
			// Return ready code
			if ( $this -> parsing_data[0][$tag_id]['bbcode_type'] == 1)
			{
				// Lock keys
				$content = str_replace('{content}', '{content:' . $this -> parsing_key . '}', $content);
				$content = str_replace('{param}', '{param:' . $this -> parsing_key . '}', $content);
				$param = str_replace('{content}', '{content:' . $this -> parsing_key . '}', $param);
				$param = str_replace('{param}', '{param:' . $this -> parsing_key . '}', $param);
				
				// Replace
				return str_replace( ':' . $this -> parsing_key, '', str_replace( array('{content}', '{param}'), array($content, $param), $this -> parsing_data[0][$tag_id]['bbcode_replace']));
			}
			else
			{
				// Just replace
				return str_replace( '{content}', $content, $this -> parsing_data[0][$tag_id]['bbcode_replace']);
			}
		}
		
	}

	/**
	 * Cleans html tags from highlight
	 *
	 * @param array $matches
	 */
	
	function fixHighlight( $matches){
		
		// Return cleaned match
		return str_replace(array('{HL:' . $this -> parsing_key . '}', '{/HL:' . $this -> parsing_key . '}'), '', $matches[0]);
		
	}
	
}